﻿using System;
using PSE.ProcessManager.Common.Gateway;
using PSE.ProcessManager.Server.Process;
using System.Threading;
using System.Diagnostics;
using PSE.ProcessManager.Common.Process;
using PSE.ProcessManager.Entity.Enums;
using PSE.ProcessManager.Entity;
using System.Configuration;
using System.Reflection;
using System.IO;
using PSE.Framework.Process;
using PSE.ProcessManager.Server.Process.Configuration;
using PSE.Framework.ErrorLogging;
using PSE.Framework.Configuration;

namespace PSE.ProcessManager.Server.InteligenceModules
{
    /// <summary>
    /// Classe base para os InteligenceModules
    /// </summary>
    public class BaseInteligenceModule : IDisposable
    {
        protected BaseProcessExecutor Executor { get; set; }
        public ProcessEntity ProcessConfiguration { get; set; }

        ServiceEvents.MessageReceived onReceiveMessage;
        private InteligenceManagerInstanceProcessManager _process;
        private ExecutorProcess _executor;

        private string _executorConfigPath;
        private string _processFolderPath;

        //protected event EventHandler OnProcessReinitializing;
        public event EventHandler ProcessExited;

        public int IsProcessRunning
        {
            get
            {
                if (_process.IsChildStarted((int)this.ProcessConfiguration.Id))
                    return _process.GetChild((int)this.ProcessConfiguration.Id).ProcessId;
                else
                    return -1;
            }
        }

        public bool IsEnabled
        {
            get
            {
                ProcessConfigurationSection config = GetProcessConfigurationSection();

                if (config != null)
                    return config.Enabled;
                else
                    return true;
            }
        }

        //private string _errorMessage = "";

        public BaseInteligenceModule()
        {
            _process = InteligenceManagerInstanceProcessManager.GetInstance();

            //montar o caminho e nome do arquivo de configuracao do executor
            _executorConfigPath = Path.GetDirectoryName(System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath) + @"\";
            _processFolderPath = System.Configuration.ConfigurationManager.AppSettings["FolderProcessPath"];
        }

        /// <summary>
        /// Evento disparado ao receber uma mensagem Callback do ReceiverGateway
        /// </summary>
        public ServiceEvents.MessageReceived OnReceiveMessage
        {
            get { return onReceiveMessage; }
            set
            {
                onReceiveMessage = value;
                if (Executor != null && Executor.Gateway != null)
                    Executor.Gateway.OnReceiveMessage += value;
            }
        }



        #region RetrieveProcessStatus
        /// <summary>
        /// Retorna o estado do processo.
        /// </summary>
        /// <param name="processId">Id do processo.</param>
        /// <returns>Uma instância da classe ProcessStatus com as informações sobre o estado do processo.</returns>
        public ProcessStatus RetrieveProcessStatus(int processId)
        {
            ProcessStatus process = new ProcessStatus();
            return process;
        }
        #endregion

        #region StartProcess

        /// <summary>
        /// Inicializa o processo e conecta o gateway com o processo criado.
        /// </summary>
        /// <returns></returns>
        public void StartProcess()
        {
            string domain = null, userName = null, password = null;

            ProcessConfigurationSection config = GetProcessConfigurationSection();

            if (config != null &&
                config.Identity != null &&
                !string.IsNullOrEmpty(config.Identity.Domain) &&
                !string.IsNullOrEmpty(config.Identity.UserName))
            {
                domain = config.Identity.Domain;
                userName = config.Identity.UserName;
                password = config.Identity.Password;
            }

            _executor = _process.StartChild(this.ProcessConfiguration,
                                            _executorConfigPath,
                                            domain,
                                            userName,
                                            password);

            _executor.Restarted += new EventHandler(_executor_Restarted);
            _executor.Closed += new EventHandler(_executor_Closed);
            _executor.CheckIsAliveException += new EventHandler<ProcessExceptionEventArgs>(_executor_CheckIsAliveException);

            this.Executor.Gateway = ConnectToProcess(_executor.ProcessId);

            //Define o evento de callback do processo.
            if (onReceiveMessage != null)
                this.Executor.Gateway.OnReceiveMessage += onReceiveMessage;
        }

        private ProcessConfigurationSection GetProcessConfigurationSection()
        {
            string assemblyPath = Path.Combine(_processFolderPath, this.ProcessConfiguration.Assembly);

            if (!File.Exists(assemblyPath))
                throw new Exception(string.Format("Process assembly not found '{0}'.", assemblyPath));

            string processConfigPath = assemblyPath + ".config";

            if (!File.Exists(processConfigPath))
                throw new Exception(string.Format("Process config file not found '{0}'.", processConfigPath));

            ProcessConfigurationSection config = System.Configuration.ConfigurationManager.OpenExeConfiguration(assemblyPath).GetSection("processConfiguration") as ProcessConfigurationSection;

            return config;
        }

        #endregion

        #region ConnectToProcess

        /// <summary>
        /// Inicia a conexão ao processo.
        /// </summary>
        /// <param name="processId">Id do processo</param>
        /// <returns>Gateway com a conexão ao serviço</returns>
        private ProcessSenderGateway ConnectToProcess(int processUniqueId)
        {
            ProcessSenderGateway gateway = new ProcessSenderGateway();
            gateway.ProcessAddress = "net.pipe://localhost/Listener/" + processUniqueId.ToString();
            gateway.ProcessId = processUniqueId;
            gateway.Protocol = Protocol.NamedPipe;

            gateway.Connect();

            return gateway;
        }

        #endregion

        #region Execute

        /// <summary>
        /// Inicia a processo de execução do Inteligence Module.
        /// </summary>
        /// <param name="message">Mensagem de execução de um determinado processo.</param>
        public virtual void Execute(BaseMessage message)
        {
            //Define a mensagem e o gateway para o executor.
            this.Executor.Message = message;

            //Se o gateway for nulo, 
            //significa que ainda não existe um processo tratando esta solicitação
            if (IsProcessRunning == -1)
                StartProcess();
        }

        #endregion

        public void InitializeProcess(string clientIdentifier)
        {
            BaseMessage message = new BaseMessage();
            message.From = clientIdentifier;
            message.ProcessId = this.ProcessConfiguration.Id.GetValueOrDefault();
            message.Operation = "BaseProcess_OnInitialize";
            this.Executor.Gateway.BeginSendMessage(message);
        }

        void _executor_Restarted(object sender, EventArgs e)
        {
            try
            {
                if (this.Executor.Gateway != null)
                {
                    this.Executor.Gateway.Dispose();

                    if (onReceiveMessage != null)
                        this.Executor.Gateway.OnReceiveMessage -= onReceiveMessage;
                }

                this.Executor.Gateway = ConnectToProcess(_executor.ProcessId);

                //Define o evento de callback do processo.
                if (onReceiveMessage != null)
                    this.Executor.Gateway.OnReceiveMessage += onReceiveMessage;

                this.InitializeProcess(string.Empty);
            }
            catch (Exception ex)
            {
                this.HandleException(new Exception(string.Format("Error initializing restarted process '{0}'", _executor.Name), ex));

                _executor.Dispose();
            }
        }

        void _executor_Closed(object sender, EventArgs e)
        {
            _executor.Restarted -= new EventHandler(_executor_Restarted);
            _executor.Closed -= new EventHandler(_executor_Closed);
            _executor.CheckIsAliveException -= new EventHandler<ProcessExceptionEventArgs>(_executor_CheckIsAliveException);

            if (ProcessExited != null)
                ProcessExited(this, new EventArgs());
        }

        void _executor_CheckIsAliveException(object sender, ProcessExceptionEventArgs e)
        {
            this.HandleException(new Exception(string.Format("Unable to restart the process '{0}'", e.Process.Name), e.Exception));
            e.Handled = true;
        }

        private void HandleException(Exception ex)
        {
            ErrorLogger.WriteLog(LogType.Error, ex);
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_executor != null)
                _executor.Dispose();
        }

        #endregion
    }
}
